package top.wanzc.java.basic.jdk8.feature;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.security.PrivilegedAction;
import java.util.Locale;
import java.util.function.Function;

/**
 * reading property by lambda expression
 */
public class ReadingPropertyByLambda {
	
	@FunctionalInterface
	static interface CusFunction<T, R> extends Function<T, R>, Serializable {
	}
	
	static class PropertyNamer {
	    private PropertyNamer() {
	    }

	    /**
	     * 方法名转成属性名
	     */
	    public static String methodToProperty(String name) {
	        if (name.startsWith("is")) {
	            name = name.substring(2);
	        } else {
	            if (!name.startsWith("get") && !name.startsWith("set")) {
	                throw new RuntimeException("Error parsing property name '" + name + "'.  Didn't start with 'is', 'get' or 'set'.");
	            }

	            name = name.substring(3);
	        }

	        if (name.length() == 1 || name.length() > 1 && !Character.isUpperCase(name.charAt(1))) {
	            name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
	        }

	        return name;
	    }

	    public static boolean isProperty(String name) {
	        return isGetter(name) || isSetter(name);
	    }

	    public static boolean isGetter(String name) {
	        return name.startsWith("get") && name.length() > 3 || name.startsWith("is") && name.length() > 2;
	    }

	    public static boolean isSetter(String name) {
	        return name.startsWith("set") && name.length() > 3;
	    }
	}




	
	static class LambdaMetaCache {

	    private Class<?> clazz;

	    private String methodName;

	    public LambdaMetaCache(Class<?> clazz, String methodName) {
	        this.clazz = clazz;
	        this.methodName = methodName;
	    }

	    public Class<?> getClazz() {
	        return clazz;
	    }

	    public void setClazz(Class<?> clazz) {
	        this.clazz = clazz;
	    }

	    public String getMethodName() {
	        return methodName;
	    }

	    public void setMethodName(String methodName) {
	        this.methodName = methodName;
	    }
	}
	
	static class LambdaUtils {

	    /**
	     * lambda表达式反序列化
	     *
	     * @param func lambda表达式
	     */
	    private static <T> SerializedLambda resolve(CusFunction<T, ?> func) {
	        try {
	            Method method = func.getClass().getDeclaredMethod("writeReplace");
	            method.setAccessible(true);
	            SerializedLambda serializedLambda =  (SerializedLambda) method.invoke(func);
	            return serializedLambda;
	        } catch (Exception e) {
	            // 反序列化解析
	            return extractLambda(func);
	        }
	    }

	    public static <T> LambdaMetaCache extractLambdaMeta(CusFunction<T, ?> func) {
	        SerializedLambda resolve = resolve(func);
	        return new LambdaMetaCache(getClass(resolve), getMethodName(resolve));

	    }

	    /**
	     * 读取Lambda的方法名
	     *
	     * @param lambda lambda表达式序列化对象
	     * @return 方法名
	     */
	    private static String getMethodName(SerializedLambda lambda) {
	        return lambda.getImplMethodName();
	    }

	    /**
	     * 读取Lambda的类型信息
	     *
	     * @param lambda lambda表达式序列化对象
	     * @return 类型信息
	     */
	    private static Class<?> getClass(SerializedLambda lambda) {
	        String instantiatedMethodType = lambda.getImplClass();
	        String instantiatedType = instantiatedMethodType.replace("/", ".");
	        try {
	            // 加载类
	            return loadClass(instantiatedType, getClassLoaders(null));
	        } catch (ClassNotFoundException e) {
	            throw new RuntimeException("找不到指定的class！请仅在明确确定会有 class 的时候，调用该方法", e);
	        }
	    }

	    /**
	     * 反序列化
	     *
	     * @param serializable 可序列化对象
	     */
		private static SerializedLambda extractLambda(Serializable serializable) {
			try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
					ObjectOutputStream oos = new ObjectOutputStream(baos)) {
				oos.writeObject(serializable);
				oos.flush();
				try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray())) {
					@Override
					protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
						Class<?> clazz = super.resolveClass(desc);
						System.out.println("resolveClass:" + clazz.getName());
						return clazz == java.lang.invoke.SerializedLambda.class ? SerializedLambda.class : clazz;
					}
				}) {
					Object result = ois.readObject();
					System.out.println(result.getClass().getName());
					return (SerializedLambda) result;
				}
			} catch (IOException | ClassNotFoundException e) {
				throw new RuntimeException(e);
			}
		}

	    /**
	     * 类加载器列表
	     */
	    private static ClassLoader[] getClassLoaders(ClassLoader classLoader) {
	        return new ClassLoader[] {
	            classLoader,
	            Thread.currentThread().getContextClassLoader(),
	            LambdaUtils.class.getClassLoader()};
	    }

	    /**
	     * 加载类
	     *
	     * @param className    类名
	     * @param classLoaders 类加载器
	     * @return 类型
	     */
	    private static Class<?> loadClass(String className, ClassLoader[] classLoaders) throws ClassNotFoundException {
	        for (ClassLoader classLoader : classLoaders) {
	            if (classLoader != null) {
	                try {
	                    return Class.forName(className, true, classLoader);
	                } catch (ClassNotFoundException e) {
	                    // ignore
	                }
	            }
	        }
	        throw new ClassNotFoundException("Cannot find class: " + className);
	    }

	    /**
	     * 为安全访问创建的对象信息
	     *
	     * @param <T>
	     */
	    static class CusPrivilegedAction<T extends AccessibleObject> implements PrivilegedAction<T> {
	        private final T obj;

	        public CusPrivilegedAction(T obj) {
	            this.obj = obj;
	        }

	        @Override
	        public T run() {
	            obj.setAccessible(true);
	            return obj;
	        }
	    }
	}





	static class TestA implements java.io.Serializable {
	    String name = "aaaa";
	    // writeReplace执行序列化替换对象，反序列化时将会变成对象B
	    private final Object writeReplace() {
	        return new TestB();
	    }
	    public String getName() {
	    	return name;
	    }
	}
	
	static class TestB implements java.io.Serializable {
	    String name = "bbbb";
	}

	public static void main(String[] args) {
		testWriteReplace();
		testReadLambdaProprtityByReflection();
        
	}
	
	private static void testWriteReplace() {
		try (ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("./osa"))) {
            os.writeObject(new TestA());
            os.flush();
            os.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // 反序列化 TestA
        try (ObjectInputStream is = new ObjectInputStream(new FileInputStream("./osa"))) {
            Object obj = is.readObject();
            is.close();
            System.out.println("类名："+ obj.getClass().getName());
            System.out.println("属性值：" + ((TestB)obj).name);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
	}
	
	private static void testReadLambdaProprtityByReflection() {
		LambdaMetaCache lambdaMetaCache = LambdaUtils.extractLambdaMeta(TestA::getName);
        System.out.println(lambdaMetaCache.getClazz().getName());
        System.out.println(lambdaMetaCache.getMethodName());
        System.out.println(PropertyNamer.methodToProperty(lambdaMetaCache.getMethodName()));
	}
}
